스프링 시큐리티
1. 개요
1. 개요
스프링 시큐리티는 2003년에 최초 등장한, 자바 애플리케이션, 특히 스프링 프레임워크 기반 애플리케이션을 위한 인증 및 접근 제어 기능을 제공하는 프레임워크이다. 이 프레임워크는 Pivotal Software에 의해 개발 및 관리되며, 애플리케이션 보안과 웹 보안 분야에서 널리 사용된다.
주요 용도는 웹 애플리케이션의 보안 요구사항을 처리하는 것으로, 사용자 로그인, 권한 검사, 공격 방어 등을 표준화된 방식으로 구현할 수 있게 한다. 또한 서비스 계층의 메서드 수준에서 보안 규칙을 적용하는 메서드 시큐리티 기능을 제공하여, 애플리케이션 전반에 걸쳐 일관된 보안 정책을 구축하는 데 기여한다.
스프링 시큐리티는 강력하면서도 확장성이 뛰어난 구조를 가지고 있어, 기본적인 폼 로그인부터 OAuth 2.0, JWT 같은 현대적인 인증 방식을 지원하는 모듈까지 다양한 보안 시나리오에 적용할 수 있다. 이로 인해 기업용 소프트웨어부터 마이크로서비스 아키텍처에 이르기까지 다양한 규모와 복잡도의 자바 애플리케이션에서 핵심 보안 솔루션으로 자리 잡았다.
2. 아키텍처
2. 아키텍처
2.1. 인증(Authentication)
2.1. 인증(Authentication)
인증은 사용자나 시스템이 자신이 주장하는 주체임을 증명하는 과정이다. 스프링 시큐리티에서 인증은 보안 체계의 핵심적인 첫 단계로, 주로 사용자의 신원을 확인하는 데 사용된다. 인증이 성공하면 애플리케이션은 해당 사용자에게 적절한 권한을 부여하고, 이후의 인가 과정에서 접근을 통제하는 근거로 활용한다.
인증 과정은 일반적으로 사용자가 제출한 자격 증명(예: 아이디와 비밀번호)을 시스템에 저장된 신뢰할 수 있는 정보와 비교하여 이루어진다. 스프링 시큐리티는 이 과정을 처리하는 핵심 인터페이스로 AuthenticationManager를 제공한다. 이 매니저는 실제 인증 로직을 수행하는 하나 이상의 AuthenticationProvider에게 작업을 위임한다. 가장 일반적인 제공자는 DaoAuthenticationProvider로, UserDetailsService를 통해 사용자 정보를 조회하고 PasswordEncoder를 사용해 제출된 비밀번호를 검증한다.
인증이 성공적으로 완료되면, 결과물은 Authentication 객체로 캡슐화된다. 이 객체는 인증된 주체의 정보, 자격 증명, 부여된 권한(GrantedAuthority) 목록을 담고 있다. 이 객체는 이후 스프링 시큐리티의 인가 결정에 사용되며, SecurityContext에 저장되어 현재 요청 스레드 내에서 보안 정보에 접근할 수 있게 한다. 이를 통해 개발자는 어디서나 SecurityContextHolder.getContext().getAuthentication()과 같은 코드로 현재 인증된 사용자 정보를 얻을 수 있다.
스프링 시큐리티는 다양한 인증 방식을 지원하여 애플리케이션 요구사항에 맞게 유연하게 구성할 수 있다. 기본적인 폼 로그인 방식 외에도, HTTP Basic 인증, OAuth 2.0, OIDC(OpenID Connect), JWT(JSON Web Token) 기반 인증 등을 통합할 수 있다. 이러한 인증 메커니즘은 필터 체인 내에 위치한 특수 필터들(예: UsernamePasswordAuthenticationFilter, BearerTokenAuthenticationFilter)에 의해 처리된다.
2.2. 인가(Authorization)
2.2. 인가(Authorization)
인가는 인증된 사용자나 시스템이 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 결정하는 과정이다. 스프링 시큐리티는 세밀한 접근 제어를 위해 다양한 수준에서 인가를 지원한다.
가장 일반적인 방식은 HTTP 요청 수준의 인가로, URL 패턴에 따라 접근 권한을 설정한다. 이를 통해 특정 페이지나 API 엔드포인트에 대한 접근을 역할 기반으로 제한할 수 있다. 또한 애노테이션을 이용한 메서드 수준 인가를 지원하여, 서비스 계층의 비즈니스 로직 실행 권한을 제어할 수 있다.
인가 결정은 AccessDecisionManager를 중심으로 이루어지며, 구성된 AccessDecisionVoter들이 투표를 통해 최종 권한 부여 여부를 결정한다. 사용자의 권한(GrantedAuthority)은 일반적으로 인증 과정에서 로드되며, SecurityContext에 보관되어 인가 검사 시 활용된다.
2.3. 보안 컨텍스트(SecurityContext)
2.3. 보안 컨텍스트(SecurityContext)
보안 컨텍스트는 스프링 시큐리티의 핵심 추상화 중 하나로, 현재 스레드와 연결된 인증 정보를 담고 있는 저장소 역할을 한다. 이는 스레드 로컬을 기반으로 동작하여, 각 HTTP 요청을 처리하는 스레드마다 독립적인 인증 정보를 유지할 수 있게 한다. 애플리케이션 내 어디서나 SecurityContextHolder.getContext()를 호출하여 현재 사용자의 인증 객체에 접근할 수 있는 표준화된 방법을 제공한다.
보안 컨텍스트의 주요 구성 요소는 Authentication 객체이다. 이 객체는 인증 주체의 신원(UserDetails), 자격 증명, 그리고 부여된 권한(GrantedAuthority) 목록을 포함한다. 사용자가 로그인하면 인증 프로세스를 통해 생성된 Authentication 객체가 보안 컨텍스트에 저장된다. 이후 인가 검사나 비즈니스 로직에서 사용자 정보를 참조해야 할 때는 이 보안 컨텍스트에서 인증 객체를 조회하여 사용한다.
보안 컨텍스트의 저장 전략은 SecurityContextHolder의 설정에 따라 달라질 수 있다. 기본적으로는 MODE_THREADLOCAL 전략을 사용하지만, 자식 스레드와의 공유가 필요한 비동기 처리나 특정 서블릿 컨테이너 환경에서는 MODE_INHERITABLETHREADLOCAL이나 MODE_GLOBAL 전략으로 변경할 수 있다. 또한 스프링 시큐리티 필터 체인은 요청 처리가 완료되면 해당 스레드의 보안 컨텍스트를 자동으로 정리하여, 인증 정보가 다음 요청에 누출되지 않도록 보장한다.
이러한 구조 덕분에 개발자는 복잡한 세션 관리나 서블릿 API에 직접 의존하지 않고도, 일관된 방식으로 전역적인 보안 정보에 접근할 수 있다. 이는 서비스 계층에서 메서드 시큐리티 어노테이션을 사용하거나, 컨트롤러에서 @AuthenticationPrincipal 어노테이션으로 사용자 정보를 주입받는 등 다양한 보안 기능의 기반이 된다.
2.4. 필터 체인(Filter Chain)
2.4. 필터 체인(Filter Chain)
필터 체인은 스프링 시큐리티가 웹 애플리케이션의 보안 요청 처리를 위한 핵심 메커니즘이다. 이는 서블릿 필터의 체인 형태로 구성되며, 들어오는 모든 HTTP 요청은 이 체인을 순차적으로 통과하여 다양한 보안 검사와 처리를 거치게 된다. 각 필터는 특정 보안 책임을 담당하여, 인증 확인, 권한 검사, CSRF 토큰 검증 등의 작업을 수행한다.
체인의 구성은 SecurityFilterChain 빈을 통해 정의된다. 개발자는 이 빈을 설정하여 특정 URL 패턴에 적용될 필터들의 순서와 종류를 제어할 수 있다. 예를 들어, 정적 리소스 경로에는 보안 필터를 적용하지 않거나, API 엔드포인트에는 JWT 검증 필터만 적용하도록 구성할 수 있다. 이렇게 요청 경로에 따라 다른 필터 체인을 적용하는 것이 가능하다.
필터 체인의 전형적인 처리 흐름은 다음과 같다. 먼저 SecurityContextPersistenceFilter가 요청의 시작과 끝에서 보안 컨텍스트를 관리한다. 이후 UsernamePasswordAuthenticationFilter는 폼 로그인을 처리하고, BasicAuthenticationFilter는 HTTP 기본 인증을 담당한다. 요청이 인증된 후에는 FilterSecurityInterceptor가 최종적으로 접근 권한을 확인하여 요청을 허용하거나 거부한다.
이러한 아키텍처는 관심사의 분리 원칙을 잘 보여준다. 각 필터가 단일 책임을 가지도록 설계되어 유지보수와 테스트가 용이하며, 새로운 보안 요구사항이 생겼을 때 커스텀 필터를 생성하여 체인에 쉽게 추가할 수 있는 확장성을 제공한다. 따라서 필터 체인은 스프링 시큐리티가 강력하면서도 유연한 보안 프레임워크가 될 수 있는 기반이 된다.
3. 핵심 구성 요소
3. 핵심 구성 요소
3.1. UserDetailsService
3.1. UserDetailsService
UserDetailsService는 스프링 시큐리티의 핵심 인터페이스 중 하나로, 사용자 인증 과정에서 사용자의 정보를 로드하는 역할을 담당한다. 이 인터페이스는 사용자 이름(username)을 입력받아, 해당 사용자에 대한 정보를 포함한 UserDetails 객체를 반환하도록 설계되었다. 스프링 시큐리티의 인증 메커니즘이 사용자 정보를 조회할 때 이 인터페이스를 통해 표준화된 방식으로 데이터에 접근한다.
주요 메서드는 loadUserByUsername(String username) 하나로, 이 메서드는 주어진 사용자 이름에 해당하는 UserDetails 객체를 반환해야 한다. UserDetails 객체에는 사용자의 비밀번호, 활성화 여부, 계정 만료 여부, 자격 증명 만료 여부, 계정 잠금 여부와 같은 인증에 필요한 정보와 함께, 사용자가 가진 권한(GrantedAuthority) 목록이 포함되어 있다. 이 권한 목록은 이후 인가 과정에서 접근 제어를 결정하는 데 사용된다.
개발자는 애플리케이션의 사용자 데이터가 저장된 위치(관계형 데이터베이스, LDAP, 인메모리 데이터베이스 등)에 맞춰 이 인터페이스를 구현해야 한다. 가장 일반적인 방법은 데이터 접근 객체를 주입받아 데이터베이스에서 사용자 정보를 조회한 후, UserDetails 객체를 생성하여 반환하는 것이다. 스프링 시큐리티는 편의를 위해 JDBC를 이용한 기본 구현체나 인메모리 구현체도 제공한다.
UserDetailsService의 구현체는 스프링 시큐리티 설정에서 AuthenticationProvider나 DaoAuthenticationProvider와 연결되어 인증 흐름에 통합된다. 이를 통해 스프링 시큐리티는 데이터 소스의 종류와 상관없이 일관된 방식으로 사용자 정보를 조회하고 인증할 수 있게 된다.
3.2. PasswordEncoder
3.2. PasswordEncoder
PasswordEncoder는 스프링 시큐리티에서 사용자의 비밀번호를 안전하게 저장하고 검증하기 위한 핵심 인터페이스이다. 이 컴포넌트는 평문 비밀번호를 단방향 암호화하여 저장하고, 이후 로그인 시 입력된 비밀번호와 저장된 값을 비교하는 역할을 담당한다. 이를 통해 데이터베이스에 비밀번호가 노출되더라도 원문을 쉽게 알아낼 수 없도록 보호한다.
주요 구현체로는 BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder 등이 있다. 이 중 BCryptPasswordEncoder는 강력한 해시 알고리즘을 사용하며, 내부적으로 솔트(Salt)를 자동 생성하여 같은 비밀번호라도 다른 해시값을 만들어내기 때문에 가장 널리 권장된다. 개발자는 애플리케이션의 보안 요구사항에 따라 적절한 인코더를 선택하여 구성할 수 있다.
스프링 시큐리티 5 버전부터는 암호화 방식에 대한 강력한 권고가 반영되어, 이전에 흔히 사용되던 MD5나 SHA-256 등의 단순 해시 함수만을 사용하는 방식은 더 이상 기본으로 지원하지 않는다. 대신 위에 언급된 현대적인 패스워드 기반 키 유도 함수를 사용하는 구현체를 적용해야 한다. 이는 레인보우 테이블 공격과 같은 취약점으로부터 시스템을 보호하기 위함이다.
설정은 일반적으로 SecurityFilterChain을 구성하는 자바 설정 클래스 내에서 PasswordEncoder 빈(Bean)을 정의하는 방식으로 이루어진다. 이렇게 등록된 빈은 UserDetailsService가 사용자 정보를 조회할 때, 혹은 사용자 가입 시 비밀번호를 암호화하여 저장할 때 자동으로 활용된다.
3.3. SecurityFilterChain
3.3. SecurityFilterChain
SecurityFilterChain은 스프링 시큐리티의 핵심 구성 요소로, 애플리케이션의 HTTP 요청을 처리하는 보안 필터들의 순서화된 목록을 정의한다. 이는 서블릿 필터 체인의 개념을 기반으로 하여, 들어오는 모든 요청이 일련의 보안 필터를 통과하도록 구성한다. 각 필터는 인증, 인가, CSRF 보호, 세션 관리 등 특정 보안 작업을 담당하며, 필터 체인을 통해 요청이 순차적으로 처리되어 최종적으로 보호된 자원에 도달하거나 접근이 거부된다.
개발자는 SecurityFilterChain 빈(Bean)을 구성하여 애플리케이션의 보안 정책을 세밀하게 제어할 수 있다. 이를 통해 특정 URL 패턴에 대해서는 다른 필터 체인을 적용하거나, 특정 경로는 보안 검사를 완전히 생략하는 등의 설정이 가능하다. 예를 들어, 정적 리소스 경로는 인증을 생략하고, API 엔드포인트는 JWT 검증 필터를 적용하는 방식으로 다중 체인을 구성하는 것이 일반적이다.
SecurityFilterChain의 구성은 주로 자바 설정 클래스 내에서 SecurityFilterChain 타입의 @Bean을 정의하여 이루어진다. 이때 HttpSecurity 객체를 사용하여 필터들의 순서와 동작을 커스터마이즈한다. 스프링 부트의 자동 설정은 기본적인 필터 체인을 제공하지만, 대부분의 프로덕션 환경에서는 애플리케이션 요구사항에 맞게 필터 체인을 재정의한다. 이는 스프링 시큐리티가 강력한 보안 기능을 제공하면서도 높은 유연성을 갖는 이유 중 하나이다.
3.4. AuthenticationProvider
3.4. AuthenticationProvider
AuthenticationProvider는 스프링 시큐리티의 인증 아키텍처에서 실제 인증 로직을 수행하는 핵심 구성 요소이다. 인증 요청을 처리하는 AuthenticationManager는 하나 이상의 AuthenticationProvider를 가지고 있으며, 사용자가 제출한 자격 증명(예: 아이디와 비밀번호)을 검증하는 책임을 위임한다. 이는 인증 방식을 유연하게 구성하고, 다양한 인증 메커니즘(예: 데이터베이스 조회, LDAP 연동, OAuth 2.0)을 지원할 수 있게 하는 디자인 패턴이다.
AuthenticationProvider는 주로 authenticate(Authentication authentication) 메서드를 구현하여 인증 과정을 처리한다. 이 메서드는 사용자로부터 전달받은 Authentication 객체(예: UsernamePasswordAuthenticationToken)를 파라미터로 받아, 내부 로직을 통해 자격 증명을 검증한다. 검증이 성공하면 권한 정보가 채워진 새로운 Authentication 객체를 반환하고, 실패하면 AuthenticationException을 발생시킨다. 일반적으로 이 과정에서 UserDetailsService를 호출하여 사용자 정보를 조회하고, PasswordEncoder를 사용해 저장된 비밀번호와 입력된 비밀번호를 비교한다.
스프링 시큐리티는 여러 기본 AuthenticationProvider 구현체를 제공한다. 가장 대표적인 예는 DaoAuthenticationProvider로, 이는 데이터베이스에 저장된 사용자 정보를 기반으로 한 인증을 처리한다. 개발자는 특정 인증 요구사항에 맞게 AuthenticationProvider 인터페이스를 직접 구현하여 커스텀 인증 로직(예: 생체 인증, OTP 검증)을 만들 수도 있다. 이를 통해 표준 인증 흐름을 확장하거나 완전히 새로운 인증 방식을 통합할 수 있다.
AuthenticationProvider는 SecurityFilterChain 내의 필터들에 의해 트리거되며, 성공적인 인증 후에는 인증된 사용자 정보를 SecurityContext에 저장한다. 이 구성 요소는 인증 로직의 세부 사항을 캡슐화함으로써 애플리케이션의 보안 계층을 모듈화하고 유지보수성을 높이는 데 기여한다.
4. 설정 방법
4. 설정 방법
4.1. 자바 기반 설정
4.1. 자바 기반 설정
자바 기반 설정은 XML 설정 파일을 사용하는 전통적인 방식 대신, 자바 코드를 통해 스프링 시큐리티를 구성하는 현대적인 방법이다. 이 방식은 스프링 프레임워크 3.1부터 도입된 자바 설정의 일환으로, 타입 안전성을 제공하고 리팩토링에 유리하며, 설정의 유연성을 크게 향상시킨다.
구성의 중심에는 @Configuration 어노테이션이 붙은 설정 클래스가 위치한다. 이 클래스 내부에서는 SecurityFilterChain 타입의 빈을 정의하여 HTTP 요청에 대한 보안 규칙을 설정한다. 주요 설정 요소로는 특정 URL 패턴에 대한 접근 권한 설정, 폼 로그인 또는 HTTP 기본 인증 방식의 선택, CSRF 보호 기능의 활성화 여부 등을 코드로 명시할 수 있다.
이 설정 방식은 스프링 부트와의 통합에서 특히 강력한 장점을 발휘한다. 스프링 부트의 자동 구성 기능은 기본적인 보안 설정을 제공하며, 개발자는 자바 기반 설정을 통해 이 기본값을 쉽게 재정의하거나 세부적으로 조정할 수 있다. 결과적으로 애플리케이션의 보안 요구사항에 맞춘 정교한 구성이 가능해진다.
4.2. 의존성 주입
4.2. 의존성 주입
스프링 시큐리티는 스프링 프레임워크의 핵심 철학 중 하나인 의존성 주입(Dependency Injection, DI)을 기반으로 설계되어 있다. 이를 통해 보안 구성 요소들 간의 결합도를 낮추고, 유연한 설정과 테스트 용이성을 제공한다. 개발자는 빈(Bean)으로 등록된 다양한 보안 컴포넌트를 필요에 맞게 교체하거나 확장할 수 있으며, 스프링 IoC 컨테이너가 이들의 생명주기와 의존 관계를 관리한다.
주요 보안 구성 요소인 UserDetailsService, PasswordEncoder, AuthenticationProvider 등은 모두 스프링 빈으로 정의되어 SecurityFilterChain 설정 내에서 주입되어 사용된다. 예를 들어, 데이터베이스 기반 사용자 인증을 구현하려면 UserDetailsService 인터페이스를 구현한 커스텀 빈을 생성하고, 이를 인증 관리자에 주입하면 된다. 이 방식은 애플리케이션의 다른 비즈니스 로직과 동일한 방식으로 보안 계층을 구성하고 통합할 수 있게 한다.
스프링 부트와의 통합에서는 의존성 주입이 더욱 간소화된다. 스프링 부트 스타터 의존성을 프로젝트에 추가하면 자동 구성(Auto-Configuration)이 활성화되어 많은 보안 설정이 기본값으로 제공된다. 개발자는 이때 자동으로 생성된 보안 관련 빈들을 그대로 사용하거나, @Configuration 클래스에서 자신만의 빈을 정의하여 오버라이드하는 방식으로 세밀한 제어가 가능하다. 이는 관례보다 구성(Convention over Configuration) 원칙을 잘 보여준다.
이러한 의존성 주입 기반의 아키텍처는 단위 테스트와 통합 테스트를 용이하게 만드는 장점도 있다. 보안 로직이 강하게 결합된 전통적인 방식과 달리, 테스트 시에는 실제 PasswordEncoder나 인가 결정자를 목(Mock) 객체로 쉽게 대체할 수 있어, 순수한 비즈니스 로직에 집중한 테스트를 작성할 수 있다. 결과적으로 스프링 시큐리티는 강력한 보안 기능을 제공하면서도 스프링 생태계의 유연성과 개발자 친화성을 유지하는 프레임워크이다.
5. 주요 기능
5. 주요 기능
5.1. 폼 로그인
5.1. 폼 로그인
폼 로그인은 스프링 시큐리티가 제공하는 가장 기본적이고 전통적인 인증 방식이다. 사용자가 웹 브라우저를 통해 아이디와 비밀번호를 입력하는 HTML 폼을 제공하고, 이를 서버가 처리하여 사용자 세션을 생성하는 과정을 말한다. 이 기능은 스프링 시큐리티의 자동 설정을 통해 매우 간단하게 활성화할 수 있으며, 커스터마이징이 용이하다는 특징이 있다.
기본적인 폼 로그인 흐름은 사용자가 보호된 자원에 접근하면 로그인 페이지로 리다이렉트되고, 사용자가 자격 증명을 제출하면 서버는 UserDetailsService를 통해 사용자 정보를 조회하고 PasswordEncoder를 이용해 비밀번호를 검증한다. 인증이 성공하면 세션이 생성되고, 사용자는 원래 요청한 자원으로 이동한다. 이 과정에서 CSRF 토큰이 자동으로 적용되어 사이트 간 요청 위조 공격을 방어한다.
개발자는 SecurityFilterChain을 구성할 때 formLogin() 메서드를 사용하여 폼 로그인을 설정한다. 이를 통해 로그인 페이지 경로, 로그인 처리 URL, 로그인 성공 및 실패 시의 동작, 사용자명과 비밀번호의 파라미터 이름 등을 세밀하게 제어할 수 있다. 또한, 기본적으로 제공되는 로그인 페이지 대신 애플리케이션 자체의 디자인이 적용된 커스텀 페이지를 사용하는 것도 일반적이다.
폼 로그인은 세션 기반 인증을 전제로 하기 때문에, 스프링 시큐리티의 세션 관리 기능과 밀접하게 연동되어 작동한다. 이 방식은 스프링 부트와의 통합을 통해 최소한의 코드로 강력한 웹 보안을 구현할 수 있게 해주며, OAuth 2.0이나 JWT와 같은 보다 현대적인 방식의 기반이 되는 핵심 메커니즘을 이해하는 데 중요하다.
5.2. OAuth 2.0 / OIDC
5.2. OAuth 2.0 / OIDC
스프링 시큐리티는 OAuth 2.0 인증 프레임워크와 OpenID Connect(OIDC) 프로토콜을 포괄적으로 지원한다. 이를 통해 애플리케이션은 구글, 깃허브, 페이스북과 같은 외부 ID 공급자를 이용한 소셜 로그인 기능을 쉽게 구현할 수 있다. OAuth 2.0은 리소스에 대한 위임된 접근 권한을 부여하는 표준이며, OIDC는 그 위에 사용자 인증 정보를 제공하는 계층이다.
스프링 시큐리티의 OAuth 2.0 지원은 주로 클라이언트 측과 리소스 서버 측 기능을 중심으로 이루어진다. 클라이언트 등록 정보를 설정 파일에 정의하고, 적절한 SecurityFilterChain을 구성하면 외부 공급자로의 인증 흐름을 처리할 수 있다. 리소스 서버 설정을 통해 JWT(JSON Web Token)나 불투명 토큰을 검증하여 보호된 자원에 대한 접근을 제어한다.
이 통합은 스프링 부트의 자동 구성 덕분에 상당히 간소화된다. spring-boot-starter-oauth2-client나 spring-boot-starter-oauth2-resource-server 같은 스타터 의존성을 추가하는 것만으로도 기본 설정이 준비된다. 개발자는 공급자별 클라이언트 ID와 비밀키 같은 세부 사항만 설정하면 된다.
확장성 측면에서 스프링 시큐리티는 다양한 OAuth 2.0 권한 부여 유형과 커스텀 사용자 정보 매핑 전략을 지원한다. 또한 Authorization Server를 직접 구현해야 하는 경우, 스프링 시큐리티 OAuth 2.0 권한 서버 모듈을 활용할 수 있다. 이를 통해 완전한 OAuth 2.0 및 OIDC 호환 시스템을 구축하는 것이 가능해진다.
5.3. 메서드 시큐리티
5.3. 메서드 시큐리티
메서드 시큐리티는 스프링 시큐리티가 제공하는 선언적 보안 접근 방식으로, 애플리케이션의 서비스 계층(비즈니스 로직)에 대한 접근 제어를 구현한다. 웹 요청 수준의 보안을 보완하여, 컨트롤러나 서비스의 특정 메서드가 실행되기 전에 사용자의 권한을 검사한다. 이를 통해 URL 패턴만으로는 세밀하게 제어하기 어려운 복잡한 비즈니스 규칙 기반의 인가 정책을 적용할 수 있다.
주요 구현 방식은 어노테이션을 사용하는 것이다. @PreAuthorize, @PostAuthorize, @Secured 등의 어노테이션을 메서드 위에 선언함으로써 접근 제어 조건을 명시한다. 예를 들어, @PreAuthorize("hasRole('ADMIN') or #userId == principal.username")과 같이 작성하면, 관리자 역할을 가졌거나 자신의 데이터에만 접근할 수 있도록 메서드 실행 전에 검증이 이루어진다. 이러한 어노테이션은 AOP(관점 지향 프로그래밍) 기술을 바탕으로 동작하며, 프록시 객체를 통해 보안 검사 로직이 삽입된다.
메서드 시큐리티를 활성화하기 위해서는 구성 클래스에 @EnableMethodSecurity 어노테이션을 추가해야 한다. 이 설정이 되어야 스프링 컨테이너가 보안 어노테이션을 처리할 수 있다. 또한, @PreAuthorize 어노테이션 내에서는 SpEL(스프링 표현 언어)을 활용하여 메서드 파라미터 값(#변수명), 반환 값, 또는 보안 컨텍스트의 인증 정보(principal)에 대한 복잡한 조건을 표현할 수 있어 매우 유연한 접근 제어가 가능하다.
이 기능은 특히 REST API나 복잡한 도메인 모델을 가진 애플리케이션에서 유용하게 사용된다. 웹 계층의 필터 체인을 통한 보안만으로는 부족한, 데이터나 행위 단위의 정교한 권한 검증이 필요할 때 메서드 시큐리티를 적용하여 애플리케이션 전반의 보안 수준을 강화할 수 있다.
5.4. CSRF 보호
5.4. CSRF 보호
CSRF 보호는 스프링 시큐리티가 제공하는 핵심 웹 보안 기능 중 하나이다. CSRF(교차 사이트 요청 위조)는 악의적인 웹사이트가 사용자의 브라우저를 통해 신뢰할 수 있는 사이트에 원치 않는 요청을 전송하게 만드는 공격 기법이다. 예를 들어, 사용자가 모르는 사이에 은행 이체나 비밀번호 변경과 같은 민감한 작업이 실행될 수 있다. 스프링 시큐리티는 이러한 공격을 방어하기 위해 기본적으로 CSRF 보호 기능을 활성화한다.
이 보호 메커니즘의 핵심은 동기화 토큰 패턴을 사용하는 것이다. 서버는 클라이언트(일반적으로 브라우저)에 고유하고 예측 불가능한 토큰 값을 생성하여 제공한다. 이후 상태를 변경하는 요청(예: POST, PUT, PATCH, DELETE)이 발생하면 클라이언트는 이 토큰을 요청에 포함시켜야 한다. 서버는 매 요청마다 전달받은 토큰을 세션에 저장된 값과 비교하여 검증한다. 악의적인 사이트는 이 토큰 값을 알 수 없으므로 유효한 요청을 위조할 수 없다.
스프링 시큐리티의 CSRF 보호는 타임리프나 JSP 같은 서버 사이드 템플릿 엔진과의 통합을 통해 편리하게 적용할 수 있다. 예를 들어, 타임리프 폼에서는 th:action 속성을 사용하면 자동으로 CSRF 토큰이 숨은 입력 필드(<input type="hidden">)로 추가된다. 반면, REST API나 싱글 페이지 애플리케이션(SPA)과 같이 세션을 사용하지 않는 스테이트리스(stateless) 아키텍처에서는 CSRF 보호를 명시적으로 비활성화하는 경우가 많다. 이는 JWT 같은 토큰 기반 인증 방식에서는 CSRF 공격의 위험이 상대적으로 낮기 때문이다.
설정은 SecurityFilterChain을 구성하는 과정에서 csrf() 메서드를 통해 제어할 수 있다. 특정 요청 경로에 대해서만 보호를 예외로 두거나, 커스텀 CsrfTokenRepository를 구현하여 토큰의 저장 및 검증 방식을 변경하는 등의 확장이 가능하다. 이를 통해 개발자는 애플리케이션의 보안 요구사항에 맞게 유연하게 CSRF 정책을 조정할 수 있다.
5.5. 세션 관리
5.5. 세션 관리
세션 관리는 스프링 시큐리티가 사용자의 인증 상태를 유지하고 제어하는 핵심 메커니즘이다. 사용자가 로그인에 성공하면 서버 측에 세션이 생성되고, 이후 요청에서 이 세션을 통해 사용자를 식별하여 인증 절차를 반복하지 않도록 한다. 스프링 시큐리티는 이 세션의 생성, 유지, 무효화, 보안 정책 적용을 위한 다양한 기능을 제공한다.
주요 구성 요소로는 SessionManagementFilter와 SessionAuthenticationStrategy가 있다. SessionManagementFilter는 필터 체인 내에서 세션 관련 작업을 처리하며, SessionAuthenticationStrategy는 인증 성공 시 세션 정책(예: 동일 사용자의 다중 세션 제어)을 실행한다. 또한 SecurityContextRepository를 통해 보안 컨텍스트를 세션에 저장하고 조회하는 방식을 관리할 수 있다.
스프링 시큐리티는 세션 정책을 세밀하게 설정할 수 있다. always, ifRequired, never, stateless 등의 정책을 통해 세션 생성 시기를 제어하며, 동시 세션 제어 기능을 통해 한 사용자가 가질 수 있는 최대 활성 세션 수를 제한하고 초과 시의 동작(이전 세션 만료 또는 새 로그인 거부)을 설정할 수 있다. 세션 고정 공격 방지를 위해 인증 시 세션 ID를 변경하는 기능이 기본적으로 활성화되어 있다.
세션 타임아웃은 서블릿 컨테이너 수준이나 스프링 부트의 server.servlet.session.timeout 속성으로 설정하며, 명시적으로 세션을 무효화하는 로그아웃 기능도 제공된다. 스프링 부트와의 통합을 통해 이러한 대부분의 설정은 자동 구성되며, SecurityFilterChain 빈을 구성하여 세션 관리 관련 동작을 세부적으로 커스터마이즈하는 것이 일반적이다.
6. 통합 및 확장
6. 통합 및 확장
6.1. Spring Boot 통합
6.1. Spring Boot 통합
스프링 부트는 스프링 프레임워크 기반 애플리케이션의 빠른 개발과 간편한 설정을 위한 도구이다. 스프링 시큐리티와 스프링 부트의 통합은 개발자가 복잡한 보안 설정을 최소화하고, 자동 구성과 스타터 의존성을 통해 보안 기능을 손쉽게 프로젝트에 적용할 수 있게 한다. spring-boot-starter-security 의존성을 프로젝트에 추가하는 것만으로도 기본적인 웹 보안 설정이 자동으로 활성화된다.
이 통합의 핵심은 자동 구성이다. 스프링 부트는 클래스패스에 스프링 시큐리티가 존재하는 것을 감지하면, 미리 정의된 보안 구성을 자동으로 적용한다. 이는 기본적인 폼 로그인 페이지 제공, 모든 요청에 대한 인증 요구, CSRF 보호 활성화, 세션 관리 등을 포함한다. 또한, 자동으로 생성된 기본 사용자 계정과 비밀번호를 로그 콘솔을 통해 제공하여 개발 초기 테스트를 용이하게 한다.
통합의 장점은 설정의 간소화와 커스터마이징의 용이성에 있다. 개발자는 SecurityFilterChain 빈을 구성하는 단일 자바 설정 클래스만 작성하여 기본 자동 구성을 쉽게 재정의할 수 있다. 여기서 URL 패턴별 접근 권한, 커스텀 로그인 페이지, OAuth 2.0 또는 JWT 같은 특정 인증 메커니즘을 설정할 수 있다. 스프링 부트의 외부 설정 기능을 활용하면, application.properties 또는 application.yml 파일을 통해 보안 관련 속성을 관리할 수도 있다.
이러한 깊은 통합 덕분에 개발자는 보안 인프라 구축보다 비즈니스 로직 구현에 더 집중할 수 있으며, 스프링 생태계의 일관된 개발 경험을 유지하면서 강력한 애플리케이션 보안을 달성할 수 있다.
6.2. JWT(JSON Web Token)
6.2. JWT(JSON Web Token)
JWT(JSON Web Token)는 스프링 시큐리티에서 세션 기반 인증 방식을 대체하거나 보완하기 위해 널리 사용되는 토큰 기반 인증 방식이다. RFC 7519 표준으로 정의된 JWT는 JSON 형식의 정보를 암호화하여 안전하게 전송하는 자기 포함 토큰이다. 이 토큰은 헤더, 페이로드, 서명의 세 부분으로 구성되어 있으며, 특히 분산 시스템이나 마이크로서비스 아키텍처에서 상태 비저장 인증을 구현하는 데 적합하다.
스프링 시큐리티는 JWT를 직접 구현하지는 않지만, jjwt나 Nimbus JOSE + JWT와 같은 서드파티 라이브러리와의 통합을 통해 JWT 기반 인증 흐름을 쉽게 구성할 수 있도록 지원한다. 일반적인 구현 방식은 사용자가 폼 로그인이나 OAuth 2.0 등을 통해 인증을 완료하면 서버가 JWT를 생성하여 클라이언트에 반환한다. 이후 클라이언트는 이 토큰을 HTTP 요청의 Authorization 헤더에 담아 서버에 전송하고, 서버는 토큰의 서명을 검증하여 요청의 유효성을 확인한다.
JWT를 스프링 시큐리티에 통합할 때는 주로 필터 체인에 커스텀 필터를 추가하는 방식을 사용한다. 이 필터는 요청 헤더에서 JWT를 추출하고, 토큰을 검증 및 파싱하여 사용자 정보를 얻은 후, SecurityContext에 Authentication 객체를 설정하는 역할을 한다. 이를 통해 기존의 세션 관리 메커니즘 없이도 사용자 인증 상태를 유지할 수 있다.
JWT 사용의 주요 장점은 서버의 상태 비저장 특성과 확장성이다. 반면, 토큰이 클라이언트 측에 저장되므로 토큰 탈취에 대한 보안 고려가 필요하며, 한번 발급된 토큰은 만료 시간 전까지 강제로 무효화하기 어렵다는 단점도 있다. 따라서 액세스 토큰의 짧은 만료 시간과 리프레시 토큰을 함께 사용하는 등의 보안 전략이 권장된다.
7. 여담
7. 여담
스프링 시큐리티는 2003년에 처음 등장하여 스프링 프레임워크 생태계의 핵심 보안 솔루션으로 자리 잡았다. 초기에는 'Acegi Security'라는 이름으로 시작했으며, 복잡한 XML 설정이 필요했으나, 이후 스프링 프레임워크에 통합되면서 현재의 이름으로 변경되고 설정이 크게 간소화되었다.
이 프레임워크는 자바 엔터프라이즈 애플리케이션의 보안 요구 사항을 충족시키기 위해 지속적으로 발전해 왔다. 특히 스프링 부트의 등장과 함께 제공되는 자동 구성 기능 덕분에, 개발자는 최소한의 설정으로도 강력한 보안 기능을 애플리케이션에 빠르게 적용할 수 있게 되었다. 이는 마이크로서비스 아키텐처와 클라우드 네이티브 개발 패러다임에서 중요한 장점으로 작용한다.
스프링 시큐리티의 성공은 풍부한 확장성과 커뮤니티 지원에 기인한다. OAuth 2.0 및 OpenID Connect와 같은 현대적인 인증 프로토콜을 공식적으로 지원하며, JWT와 같은 토큰 기반 인증 방식도 쉽게 통합할 수 있다. 또한, CSRF 공격 방어, 세션 관리, 메서드 수준 보안 등 웹 애플리케이션 보안의 다양한 측면을 포괄한다.
이처럼 스프링 시큐리티는 단순한 라이브러리를 넘어, 자바 개발자들이 표준화된 방식으로 인증과 인가를 구현할 수 있는 사실상의 표준 프레임워크가 되었다. 그 유연성과 강력함 덕분에 소규모 프로젝트부터 대규모 엔터프라이즈 시스템에 이르기까지 폭넓게 사용되고 있다.
